import pygame as pg
import numpy as np
from PIL import Image, ImageSequence


class ImageObject:
    """
    图片基类
    参数：screen：需要绘制的容器
         image_path：图片的路径（包括图片文件名）
         coordinate：图片左上角坐标，默认为(0,0)
    属性：screen：同上
         coordinate：同上
         frame_sur：图片的surface对象
         frame_array：图片像素的ndarray对象，结构为“RGBA”
         frame_shape：图片的像素高和宽
    方法：sur_to_array（静态方法）：返回一个surface对象的ndarray对象
                                参数：surface：需要转换的surface对象
                                返回值：ndarray对象，结构为“RGBA”
         is_in：返回一个点或者以这个点为起点的surface（ndarray）对象是否在frame_sur对象中或者相交
                参数：point：二元数组，需要判断的点或者另一张图片的起点
                     image：surface对象或者ndarray对象，需要判断的图片
                返回值：bool值，这个对象或者点是否在frame图片对象中
         update：对图片进行绘制
                 参数：无
                 返回值：无
    """

    def __init__(self, screen: pg.Surface, image_path: str, coordinate=(0, 0)):
        self._coordinate = coordinate
        self.screen = screen
        self._frame_sur = pg.image.load(image_path)
        self._frame_array = self.sur_to_array(self._frame_sur)

    @staticmethod
    def sur_to_array(surface: pg.Surface) -> np.ndarray:
        height, width = surface.get_size()
        rgba_array = np.zeros((height, width, 4), dtype=np.uint8)
        rgb_array = pg.surfarray.array3d(surface)
        alpha_array = pg.surfarray.array_alpha(surface)
        rgba_array[..., :3] = rgb_array
        rgba_array[..., 3] = alpha_array
        return rgba_array

    @property
    def frame_sur(self):
        return self._frame_sur

    @frame_sur.setter
    # 更新frame_sur属性时更新frame_array属性的值
    def frame_sur(self, new_image):
        if isinstance(new_image, str):
            self._frame_sur = pg.image.load(new_image)
        elif isinstance(new_image, (pg.Surface, pg.SurfaceType)):
            self._frame_sur = new_image
        self._frame_array = self.sur_to_array(self._frame_sur)

    @property
    def frame_array(self):
        return self._frame_array

    @property
    def frame_shape(self):
        return self._frame_array.shape

    @property
    def coordinate(self):
        return self._coordinate

    @coordinate.setter
    # 判断新坐标的合法性
    def coordinate(self, new_coordinate):
        if len(new_coordinate) == 2 and all([isinstance(new, (int, float)) for new in new_coordinate]):
            self._coordinate = new_coordinate

    def is_in(self, point: tuple[int | float, int | float], image: pg.Surface | np.ndarray = None) -> bool:
        if not isinstance(point, (tuple, list)) or len(point) != 2:
            return False
        x, y = int(point[0] - self._coordinate[0]), int(point[1] - self._coordinate[1])
        if image is None:
            h0, w0 = self.frame_shape[:2]
            if 0 <= x < w0 and 0 <= y < h0:
                if len(self._frame_array.shape) == 3 and self._frame_array.shape[2] == 3:
                    # RGB 图像，没有 alpha 通道，但这里我们假设只要像素存在就认为点在框架内
                    return True
                elif len(self._frame_array.shape) == 3 and self._frame_array.shape[2] == 4:
                    # RGBA 图像，检查 alpha 通道
                    if self._frame_array[y, x, 3] > 0:
                        return True
            return False

        # 处理图像的情况
        if isinstance(image, pg.Surface):
            image = self.sur_to_array(image)
        elif not isinstance(image, np.ndarray):
            return False

        h0, w0 = self.frame_shape[:2]
        h1, w1 = image.shape[:2]

        # 检查重叠
        overlap_x_min = max(0, min(x, x + w1 - w0))
        overlap_x_max = min(w0, max(x, x + w1))
        overlap_y_min = max(0, min(y, y + h1 - h0))
        overlap_y_max = min(h0, max(y, y + h1))

        if overlap_x_min < overlap_x_max and overlap_y_min < overlap_y_max:
            array1 = self._frame_array[overlap_y_min:overlap_y_max, overlap_x_min:overlap_x_max]
            array2 = image[overlap_y_min - (y - overlap_y_min):overlap_y_max - (y - overlap_y_min),
                     overlap_x_min - (x - overlap_x_min):overlap_x_max - (x - overlap_x_min)]

            # 检查是否有非透明像素重叠
            if len(array1.shape) == 3 and array1.shape[2] == 4 and len(array2.shape) == 3 and array2.shape[2] == 4:
                return np.any(np.logical_and(array1[..., 3] > 0, array2[..., 3] > 0))

        return False

    def update(self):
        self.screen.blit(self._frame_sur, self._coordinate)


class MovableImageObject(ImageObject):
    """
    可移动的图片基类
    参数：screen：需要绘制的容器
         image_path：图片的路径（包括图片文件名）
         coordinate：图片左上角坐标（也时默认未指定trajectory时的默认唯一元素），默认为(0, 0)
         trajectory_cycle_number：指定轨迹循环次数，默认为-1，即无限循环
    属性：screen：同上
         coordinate：图片在轨迹中现在所在位置的坐标，可以通过在轨迹中的位置（int值，可以大于轨迹循环周期的长度）指定或者用二元数组指定（只生效一次）
         frame_sur：图片的surface对象
         frame_array：图片像素的ndarray对象，结构为“RGBA”
         frame_shape：图片的像素高和宽
         trajectory：图片移动的轨迹，由二元数组组成的列表
         trajectory_number：图片移动到轨迹上的第几个位置（不会大于或者等于移动轨迹的总坐标数，即trajectory_cycle）
         trajectory_cycle：图片的移动轨迹有多少个坐标（不可修改，随trajectory的长度改变而改变）
         trajectory_cycle_index：图片经过了几次循环（不可修改，通过trajectory_number来增加）
         trajectory_cycle_number：指定循环的次数，到达指定次数后停留在最后点的位置
         trajectory_count：图片总共移动了多少次（该属性不可修改，只能通过设置trajectory_number或者trajectory_cycle_index来设置）
    方法：points_trajectories（静态方法）：返回点与点之间的一个坐标列表
                                       参数：args：点的坐标列表，最少需要两个点，开始的点在前面
                                            step：步长，即坐标列表中每个点的距离，默认为1
                                            decimals：保留的小数位数，默认为2
                                       返回值：一个坐标列表
         sur_to_array（静态方法）：返回一个surface对象的ndarray对象
                                参数：surface：需要转换的surface对象
                                返回值：ndarray对象，结构为“RGBA”
         is_in：返回一个点或者以这个点为起点的surface（ndarray）对象是否在frame_sur对象中或者相交
                参数：point：二元数组，需要判断的点或者另一张图片的起点
                     image：surface对象或者ndarray对象，需要判断的图片
                返回值：bool值，这个对象或者点是否在frame图片对象中
         update：按照指定的轨迹对图片进行绘制
                 参数：无
                 返回值：无
    """

    def __init__(self, screen: pg.Surface, image_path: str, coordinate=(0, 0), trajectory_cycle_number=-1):
        super().__init__(screen, image_path, coordinate)
        self.trajectory_cycle_number = trajectory_cycle_number
        self._trajectory = [self.coordinate]
        self._trajectory_cycle = len(self._trajectory)
        self._trajectory_number = 0
        self.trajectory_cycle_index = 0

    @staticmethod
    def points_trajectory(*args, step=1, decimals=2) -> list:
        def points_trajectory(p1, p2):
            p1 = np.array(p1)
            p2 = np.array(p2)
            distance = np.linalg.norm(p2 - p1)
            num_points = int(np.ceil(distance / step)) - 1
            t_values = np.linspace(0, 1, num_points + 1)
            interpolated_points = (1 - t_values[:, np.newaxis]) * p1 + t_values[:, np.newaxis] * p2
            interpolated_points = np.around(interpolated_points, decimals=decimals)
            return interpolated_points.tolist()
        trajectory = []
        point1 = args[0]
        for point2 in args[1:]:
            trajectory += points_trajectory(point1, point2)
            point1 = point2
        return trajectory

    @property
    def coordinate(self):
        return self._coordinate

    @coordinate.setter
    def coordinate(self, new_coordinate):
        if isinstance(new_coordinate, int):
            self.trajectory_number = new_coordinate
        elif len(new_coordinate) == 2 and all(isinstance(new, (int, float)) for new in new_coordinate):
            self._coordinate = new_coordinate

    @property
    def trajectory(self):
        return self._trajectory

    @trajectory.setter
    def trajectory(self, new_trajectory):
        if isinstance(new_trajectory, (list, tuple)):
            if all(isinstance(new[0], (int, float)) and isinstance(new[1], (int, float)) for new in new_trajectory):
                self._trajectory = new_trajectory
                self._trajectory_cycle = len(new_trajectory)
                self.trajectory_number = 0

    @property
    def trajectory_cycle(self):
        return self._trajectory_cycle

    @property
    def trajectory_number(self):
        return self._trajectory_number

    @trajectory_number.setter
    def trajectory_number(self, new_number):
        if ((self.trajectory_cycle_number == -1) or
                (self.trajectory_cycle_index < self.trajectory_cycle_number and new_number != self._trajectory_cycle)):
            self._trajectory_number = new_number % self._trajectory_cycle
            self.trajectory_cycle_index += new_number // self._trajectory_cycle
            self._coordinate = self._trajectory[self._trajectory_number]

    @property
    def trajectory_count(self):
        return self._trajectory_number + self.trajectory_cycle_index * self._trajectory_cycle

    def update(self):
        self.screen.blit(self._frame_sur, self.coordinate)
        self.trajectory_number += 1


class GifOImageObject(ImageObject):
    """
    gif图片基类
    参数：screen：需要绘制的容器
         image_path：图片的路径（包括图片文件名）
         coordinate：图片左上角坐标，默认为(0, 0)
         frames_cycle_number：指定gif循环次数，默认为-1，即无限循环
    属性：screen：同上
         coordinate：同上
         frame_sur：图片当前的surface对象，即frames_sur的其中一个，初始为第一个
         frame_array：图片像素的ndarray对象，结构为“RGBA”
         frame_shape：图片的像素高和宽
         frames_sur：图片的surface对象列表
         frames_cycle：图片的surface对象列表的长度（不可修改，随frames_sur的长度改变而改变）
         frame_number：当前是第几个图片surface对象（小于图片列表的长度）
         frames_cycle_index：图片已经循环的次数（只增不减，只能通过修改frame_number来增加）
         frames_cycle_number：指定的图片循环次数
         frames_count：图片总共经过多少帧（不可修改，只能通过修改frame_number来修改）
    方法：gif_to_surface（静态方法）：获取gif图片的surface列表
                                  参数：gif_path：gif图片的路径（包括文件名）
                                  返回：返回一个surface列表
         sur_to_array（静态方法）：返回一个surface对象的ndarray对象
                                参数：surface：需要转换的surface对象
                                返回值：ndarray对象，结构为“RGBA”
         is_in：返回一个点或者以这个点为起点的surface（ndarray）对象是否在frame_sur对象中或者相交
                参数：point：二元数组，需要判断的点或者另一张图片的起点
                     image：surface对象或者ndarray对象，需要判断的图片
                返回值：bool值，这个对象或者点是否在frame图片对象中
         update：对每帧图片进行绘制
                 参数：无
                 返回值：无
    """

    def __init__(self, screen: pg.Surface, gif_path: str, coordinate=(0, 0), frames_cycle_number=-1):
        super().__init__(screen, gif_path, coordinate)
        self._frames_sur = self.gif_to_surfaces(gif_path)
        self._frames_cycle = len(self._frames_sur)
        self._frame_number = 0
        self.frames_cycle_index = 0
        self.frames_cycle_number = frames_cycle_number

    @staticmethod
    def gif_to_surfaces(gif_path):
        gif_frame = Image.open(gif_path)
        frames = [pg.image.fromstring(frame.convert('RGBA').tobytes(), frame.size, 'RGBA')
                  for frame in ImageSequence.Iterator(gif_frame)]
        return frames

    @property
    def frame_sur(self):
        return self._frame_sur

    @frame_sur.setter
    def frame_sur(self, new_frame):
        if isinstance(new_frame, int):
            self.frame_number = new_frame
        elif isinstance(new_frame, pg.Surface):
            self._frame_sur = new_frame
        self._frame_array = self.sur_to_array(self._frame_sur)

    @property
    def frames_sur(self):
        return self._frames_sur

    @frames_sur.setter
    def frames_sur(self, new_frames: list[pg.Surface] | tuple[pg.Surface] | str):
        if isinstance(new_frames, (list, tuple)):
            if all(isinstance(new, pg.Surface) for new in new_frames):
                self._frames_sur = new_frames
        elif isinstance(new_frames, str):
            self._frames_sur = self.gif_to_surfaces(new_frames)
        self._frames_cycle = len(new_frames)
        self.frame_number = self._frame_number

    @property
    def frames_cycle(self):
        return self._frames_cycle

    @property
    def frame_number(self):
        return self._frame_number

    @frame_number.setter
    def frame_number(self, new_frame_number: int):
        if ((self.frames_cycle_number == -1) or
                (self.frames_cycle_index < self.frames_cycle_number and new_frame_number != self._frames_cycle)):
            self._frame_number = new_frame_number % self._frames_cycle
            self.frames_cycle_index = new_frame_number // self._frames_cycle
            self.frame_sur = self._frames_sur[self._frame_number]

    @property
    def frames_count(self):
        return self._frame_number + self.frames_cycle_index * self._frames_cycle

    def update(self):
        self.screen.blit(self.frame_sur, self._coordinate)
        self.frame_number += 1


class MovableGifImageObject(GifOImageObject):
    """
        可移动的gif图片基类
        参数：screen：需要绘制的容器
             image_path：图片的路径（包括图片文件名）
             coordinate：图片左上角坐标，默认为(0, 0)
             frames_cycle_number：指定gif循环次数，默认为-1，即无限循环
        属性：screen：同上
             coordinate：图片在轨迹中现在所在位置的坐标，可以通过在轨迹中的位置（int值，可以大于轨迹循环周期的长度）指定或者用二元数组指定（只生效一次）
             frame_sur：图片当前的surface对象，即frames_sur的其中一个，初始为第一个
             frame_array：图片像素的ndarray对象，结构为“RGBA”
             frame_shape：图片的像素高和宽
             frames_sur：图片的surface对象列表
             frames_cycle：图片的surface对象列表的长度（不可修改，随frames_sur的长度改变而改变）
             frame_number：当前是第几个图片surface对象（小于图片列表的长度）
             frames_cycle_index：图片已经循环的次数（不可修改，只能通过修改frame_number来增加）
             frames_cycle_number：指定的图片循环次数
             frames_count：图片总共经过多少帧（不可修改，只能通过修改frame_number来修改）
             trajectory：图片移动的轨迹，由二元数组组成的列表
             trajectory_number：图片移动到轨迹上的第几个位置（不会大于或者等于移动轨迹的总坐标数，即trajectory_cycle）
             trajectory_cycle：图片的移动轨迹有多少个坐标（不可修改，随trajectory的长度改变而改变）
             trajectory_cycle_index：图片经过了几次循环（不可修改，通过trajectory_number来增加）
             trajectory_cycle_number：指定循环的次数，到达指定次数后停留在最后点的位置
             trajectory_count：图片总共移动了多少次（该属性不可修改，只能通过设置trajectory_number或者trajectory_cycle_index来设置）
        方法：gif_to_surface（静态方法）：获取gif图片的surface列表
                                      参数：gif_path：gif图片的路径（包括文件名）
                                      返回：返回一个surface列表
             sur_to_array（静态方法）：返回一个surface对象的ndarray对象
                                    参数：surface：需要转换的surface对象
                                    返回值：ndarray对象，结构为“RGBA”
             points_trajectories（静态方法）：返回点与点之间的一个坐标列表
                                       参数：args：点的坐标列表，最少需要两个点，开始的点在前面
                                            step：步长，即坐标列表中每个点的距离，默认为1
                                            decimals：保留的小数位数，默认为2
                                       返回值：一个坐标列表
             is_in：返回一个点或者以这个点为起点的surface（ndarray）对象是否在frame_sur对象中或者相交
                    参数：point：二元数组，需要判断的点或者另一张图片的起点
                         image：surface对象或者ndarray对象，需要判断的图片
                    返回值：bool值，这个对象或者点是否在frame图片对象中
             update：对每帧图片进行绘制
                     参数：无
                     返回值：无
        """

    def __init__(self, screen: pg.Surface, gif_path: str, coordinate=(0, 0), frames_cycle_number=-1,
                 trajectory_cycle_number=-1):
        super().__init__(screen, gif_path, coordinate, frames_cycle_number)
        self.trajectory_cycle_number = trajectory_cycle_number
        self._trajectory = [self.coordinate]
        self._trajectory_cycle = len(self._trajectory)
        self._trajectory_number = 0
        self.trajectory_cycle_index = 0

    @staticmethod
    def points_trajectory(*args, step=1, decimals=2) -> list:
        def points_trajectory(p1, p2):
            p1 = np.array(p1)
            p2 = np.array(p2)
            distance = np.linalg.norm(p2 - p1)
            num_points = int(np.ceil(distance / step)) - 1
            t_values = np.linspace(0, 1, num_points + 2)
            interpolated_points = (1 - t_values[:, np.newaxis]) * p1 + t_values[:, np.newaxis] * p2
            interpolated_points = np.around(interpolated_points, decimals=decimals)
            return interpolated_points.tolist()

        trajectory = []
        point1 = args[0]
        for point2 in args[1:]:
            trajectory += points_trajectory(point1, point2)
            point1 = point2
        return trajectory

    @property
    def coordinate(self):
        return self._coordinate

    @coordinate.setter
    def coordinate(self, new_coordinate):
        if isinstance(new_coordinate, int):
            self.trajectory_number = new_coordinate
        elif len(new_coordinate) == 2 and all(isinstance(new, (int, float)) for new in new_coordinate):
            self._coordinate = new_coordinate

    @property
    def trajectory(self):
        return self._trajectory

    @trajectory.setter
    def trajectory(self, new_trajectory):
        if isinstance(new_trajectory, (list, tuple)):
            if all(isinstance(new[0], (int, float)) and isinstance(new[1], (int, float)) for new in new_trajectory):
                self._trajectory = new_trajectory
                self._trajectory_cycle = len(new_trajectory)
                self.trajectory_number = 0

    @property
    def trajectory_cycle(self):
        return self._trajectory_cycle

    @property
    def trajectory_number(self):
        return self._trajectory_number

    @trajectory_number.setter
    def trajectory_number(self, new_number):
        if ((self.trajectory_cycle_number == -1) or
                (self.trajectory_cycle_index < self.trajectory_cycle_number and new_number != self._trajectory_cycle)):
            self._trajectory_number = new_number % self._trajectory_cycle
            self.trajectory_cycle_index = new_number // self._trajectory_cycle
            self._coordinate = self._trajectory[self._trajectory_number]

    @property
    def trajectory_count(self):
        return self._trajectory_number + self.trajectory_cycle_index * self._trajectory_cycle

    def update(self):
        self.screen.blit(self._frame_sur, self._coordinate)
        self.frame_number += 1
        self.trajectory_number += 1
